﻿using System.Collections.Generic;
using System.Drawing;

namespace Scenario_Editor.TerrainUndo
{
    internal enum TerrainUndoTye
    {
        None,
        Material,
        Vertex
    }

    internal class TerrainUndoCore
    {
        #region Public Fields

        /// <summary>
        /// Sector coordinates and vertex Data
        /// </summary>
        public Dictionary<Point, object> coordinates;

        #endregion Public Fields

        #region Internal Fields

        internal static int cellsPerSector;

        #endregion Internal Fields

        #region Public Constructors

        public TerrainUndoCore()
        {
            this.coordinates = new Dictionary<Point, object>();
        }

        #endregion Public Constructors

        #region Public Methods

        public List<object> GetAllCellCoordinates()
        {
            List<object> allCellCoordinates = new List<object>();

            foreach (object item in this.coordinates.Values)
            {
                allCellCoordinates.Add(item);
            }
            return allCellCoordinates;
        }

        #endregion Public Methods
    }

    internal class TerrainUndoHeightItem : TerrainUndoCore
    {
        #region Public Constructors

        public TerrainUndoHeightItem()
        {
        }

        #endregion Public Constructors

        #region Public Methods

        public void AddVertexCoordinate(Point coordinate, float height)
        {
            Point sectorCoordinate = new Point(coordinate.X / cellsPerSector, coordinate.Y / cellsPerSector);
            int offsetX = sectorCoordinate.X * cellsPerSector;
            int offsetY = sectorCoordinate.Y * cellsPerSector;

            Point coordinateOffset = new Point(coordinate.X - offsetX, coordinate.Y - offsetY);

            object vertexHeight;
            if (base.coordinates.TryGetValue(sectorCoordinate, out vertexHeight))
            {
                vertexValue vertexHeights = (vertexValue)vertexHeight;
                if (!vertexHeights.coordinateUsed[coordinateOffset.X, coordinateOffset.Y])
                {
                    vertexHeights.coordinateUsed[coordinateOffset.X, coordinateOffset.Y] = true;
                    vertexHeights.height[coordinateOffset.X, coordinateOffset.Y] = height;
                    vertexHeights.Originalcoordinate[coordinateOffset.X, coordinateOffset.Y] = coordinate;
                }
            }
            else
            {
                int arraySize = TerrainUndoHeightItem.cellsPerSector;

                vertexValue vertexValue = new TerrainUndoHeightItem.vertexValue();
                vertexValue.coordinateUsed = new bool[arraySize, arraySize];

                int x = coordinateOffset.X;
                int y = coordinateOffset.Y;

                vertexValue.coordinateUsed[x, y] = true;
                vertexValue.height = new float[arraySize, arraySize];
                vertexValue.height[x, y] = height;

                vertexValue.Originalcoordinate = new Point[arraySize, arraySize];
                vertexValue.Originalcoordinate[x, y] = coordinate;

                coordinates.Add(sectorCoordinate, vertexValue);
            }
        }

        #endregion Public Methods

        #region Internal Structs

        internal struct vertexValue
        {
            #region Public Fields

            public float[,] height;
            public Point[,] Originalcoordinate;

            #endregion Public Fields

            #region Internal Fields

            internal bool[,] coordinateUsed;

            #endregion Internal Fields
        }

        #endregion Internal Structs
    }

    internal class TerrainUndoManager
    {
        #region Private Fields

        private Stack<object> redoItems;
        private Scenario_Editor.Tools.Terrain.Terrain terrain;
        private Stack<object> undoItems;

        #endregion Private Fields

        #region Public Constructors

        public TerrainUndoManager(Scenario_Editor.Tools.Terrain.Terrain terrain)
        {
            this.undoItems = new Stack<object>();
            this.redoItems = new Stack<object>();
            this.terrain = terrain;
            TerrainUndoHeightItem.cellsPerSector = terrain.numberOfCellsPerSector * 2;
        }

        #endregion Public Constructors

        #region Public Methods

        public void AddUndo(object undoObject)
        {
            this.redoItems.Clear();
            undoItems.Push(undoObject);
        }

        public void Undo()
        {
            if (this.undoItems.Count > 0)
            {
                object undoItem = undoItems.Pop();

                if (undoItem.GetType() == typeof(TerrainUndoHeightItem))
                {
                    this.redoItems.Push(this.terrain.UndoTerrainHeight((TerrainUndoHeightItem)undoItem));
                }

                if (undoItem.GetType() == typeof(TerrainUndoVertexColorItem))
                {
                    this.redoItems.Push(this.terrain.UndoTerrainVertexColor((TerrainUndoVertexColorItem)undoItem));
                }

                if (undoItem.GetType() == typeof(TerrainUndoMaterialItem))
                {
                    this.redoItems.Push(this.terrain.UndoTerrainMaterialForCells((TerrainUndoMaterialItem)undoItem));
                }
            }
        }

        #endregion Public Methods

        #region Internal Methods

        internal void Redo()
        {
            if (redoItems.Count > 0)
            {
                object redoItem = redoItems.Pop();

                if (redoItem.GetType() == typeof(TerrainUndoHeightItem))
                {
                    this.undoItems.Push(this.terrain.UndoTerrainHeight((TerrainUndoHeightItem)redoItem));
                }

                if (redoItem.GetType() == typeof(TerrainUndoVertexColorItem))
                {
                    this.undoItems.Push(this.terrain.UndoTerrainVertexColor((TerrainUndoVertexColorItem)redoItem));
                }

                if (redoItem.GetType() == typeof(TerrainUndoMaterialItem))
                {
                    this.undoItems.Push(this.terrain.UndoTerrainMaterialForCells((TerrainUndoMaterialItem)redoItem));
                }
            }
        }

        #endregion Internal Methods
    }

    internal class TerrainUndoMaterialItem : TerrainUndoCore
    {
        #region Public Constructors

        public TerrainUndoMaterialItem()
        {
        }

        #endregion Public Constructors

        #region Public Methods

        public void AddVertexCoordinate(Point coordinate, Engine.Graphics.Material material, bool isFirstPolygon)
        {
            Point sectorCoordinate = new Point(coordinate.X / cellsPerSector, coordinate.Y / cellsPerSector);
            int offsetX = sectorCoordinate.X * cellsPerSector;
            int offsetY = sectorCoordinate.Y * cellsPerSector;

            Point coordinateOffset = new Point(coordinate.X - offsetX, coordinate.Y - offsetY);

            object vertexHeight;
            if (base.coordinates.TryGetValue(sectorCoordinate, out vertexHeight))
            {
                vertexValue vertexHeights = (vertexValue)vertexHeight;
                switch (isFirstPolygon)
                {
                    case true:
                        if (!vertexHeights.coordinateUsedPoly1[coordinateOffset.X, coordinateOffset.Y])
                        {
                            vertexHeights.coordinateUsedPoly1[coordinateOffset.X, coordinateOffset.Y] = true;
                            vertexHeights.materailPoly1[coordinateOffset.X, coordinateOffset.Y] = material;
                        }
                        break;

                    case false:
                        if (!vertexHeights.coordinateUsedPoly2[coordinateOffset.X, coordinateOffset.Y])
                        {
                            vertexHeights.coordinateUsedPoly2[coordinateOffset.X, coordinateOffset.Y] = true;
                            vertexHeights.materailPoly2[coordinateOffset.X, coordinateOffset.Y] = material;
                        }
                        break;
                }
                vertexHeights.Originalcoordinate[coordinateOffset.X, coordinateOffset.Y] = coordinate;
            }
            else
            {
                int arraySize = TerrainUndoHeightItem.cellsPerSector;

                vertexValue vertexValue = new TerrainUndoMaterialItem.vertexValue();
                vertexValue.coordinateUsedPoly1 = new bool[arraySize, arraySize];
                vertexValue.coordinateUsedPoly2 = new bool[arraySize, arraySize];

                int x = coordinateOffset.X;
                int y = coordinateOffset.Y;
                //if(isFirstPolygon)
                // vertexValue.coordinateUsedPoly1[x, y] = true;
                //else
                //    vertexValue.coordinateUsedPoly2[x, y] = true;

                vertexValue.materailPoly1 = new Engine.Graphics.Material[arraySize, arraySize];
                vertexValue.materailPoly2 = new Engine.Graphics.Material[arraySize, arraySize];
                switch (isFirstPolygon)
                {
                    case true:
                        vertexValue.materailPoly1[x, y] = material;
                        vertexValue.coordinateUsedPoly1[x, y] = true;
                        break;

                    case false:
                        vertexValue.materailPoly2[x, y] = material;
                        vertexValue.coordinateUsedPoly2[x, y] = true;
                        break;
                }
                vertexValue.Originalcoordinate = new Point[arraySize, arraySize];
                vertexValue.Originalcoordinate[x, y] = coordinate;

                coordinates.Add(sectorCoordinate, vertexValue);
            }
        }

        #endregion Public Methods

        #region Internal Structs

        internal struct vertexValue
        {
            #region Public Fields

            public Engine.Graphics.Material[,] materailPoly1;
            public Engine.Graphics.Material[,] materailPoly2;
            public Point[,] Originalcoordinate;

            #endregion Public Fields

            #region Internal Fields

            internal bool[,] coordinateUsedPoly1;
            internal bool[,] coordinateUsedPoly2;

            #endregion Internal Fields
        }

        #endregion Internal Structs
    }

    internal class TerrainUndoVertexColorItem : TerrainUndoCore
    {
        #region Public Constructors

        public TerrainUndoVertexColorItem()
        {
        }

        #endregion Public Constructors

        #region Public Methods

        public void AddVertexCoordinate(Point coordinate, Engine.Graphics.IGameVertex color)
        {
            Point sectorCoordinate = new Point(coordinate.X / cellsPerSector, coordinate.Y / cellsPerSector);
            int offsetX = sectorCoordinate.X * cellsPerSector;
            int offsetY = sectorCoordinate.Y * cellsPerSector;

            Point coordinateOffset = new Point(coordinate.X - offsetX, coordinate.Y - offsetY);

            object vertexHeight;
            if (base.coordinates.TryGetValue(sectorCoordinate, out vertexHeight))
            {
                vertexColorValue vertexHeights = (vertexColorValue)vertexHeight;
                if (!vertexHeights.coordinateUsed[coordinateOffset.X, coordinateOffset.Y])
                {
                    vertexHeights.coordinateUsed[coordinateOffset.X, coordinateOffset.Y] = true;
                    vertexHeights.data[coordinateOffset.X, coordinateOffset.Y] = color;
                    vertexHeights.Originalcoordinate[coordinateOffset.X, coordinateOffset.Y] = coordinate;
                }
            }
            else
            {
                int arraySize = TerrainUndoHeightItem.cellsPerSector;

                vertexColorValue vertexValue = new TerrainUndoVertexColorItem.vertexColorValue();
                vertexValue.coordinateUsed = new bool[arraySize, arraySize];

                int x = coordinateOffset.X;
                int y = coordinateOffset.Y;

                vertexValue.coordinateUsed[x, y] = true;
                vertexValue.data = new object[arraySize, arraySize];
                vertexValue.data[x, y] = color;

                vertexValue.Originalcoordinate = new Point[arraySize, arraySize];
                vertexValue.Originalcoordinate[x, y] = coordinate;

                coordinates.Add(sectorCoordinate, vertexValue);
            }
        }

        #endregion Public Methods

        #region Internal Structs

        internal struct vertexColorValue
        {
            #region Public Fields

            /// <summary>
            /// The data which was manipulated by scenery editor game dll. eg. VertexColor
            /// </summary>
            public object[,] data;

            /// <summary>
            /// Vertex coordinate from terrainvertex.
            /// </summary>
            public Point[,] Originalcoordinate;

            #endregion Public Fields

            #region Internal Fields

            internal bool[,] coordinateUsed;

            #endregion Internal Fields
        }

        #endregion Internal Structs
    }
}